package org.concepts.java.lucene;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;

/**
 * Hello world example to introduce Lucene.
 * <p/>
 * 
 * User can provide a directory containing the files that need to be searched .
 * <p/>
 * Some of the code in this example is taken from the "Lucene in Action" book.
 * 
 * @author vvvv
 * 
 */
public class Hello {

  private static final String FIELD_FULLPATH = "fullpath";
  private static final String FIELD_FILENAME = "filename";
  private static final String FIELD_CONTENTS = "contents";

  private Analyzer analyzer;
  private IndexWriter indexer;
  private Directory indexDir;

  /**
   * Creates Hello object that can be used to search
   * 
   * @param dataDir
   *          directory in which data files reside.
   * @throws IOException
   *           Throws if there's problem accessing the passed in dataDir
   *           directory.
   */
  public Hello(File dataDir) throws IOException {
    this.index(dataDir);
  }

  /**
   * Returns the indexer.
   * 
   * @return Is never null.
   */
  public IndexWriter getIndexer() {
    return this.indexer;
  }

  /**
   * Index the files in the data directory using {@link #indexer}
   * 
   * @param dataDir
   *          Directory whose contents need to be indexed.
   * 
   * @throws IOException
   * @return
   */
  private int index(File dataDir) throws IOException {
    System.out.println("Indexing files in dir [" + dataDir + "]");
    this.indexDir = new RAMDirectory();
    this.analyzer = new StandardAnalyzer(Version.LUCENE_30);
    this.indexer = new IndexWriter(this.indexDir, analyzer, true,
        IndexWriter.MaxFieldLength.UNLIMITED);

    File[] files = dataDir.listFiles();
    for (File file : files) {
      if (!file.isDirectory() && !file.isHidden() && file.exists()
          && file.canRead()) {
        indexFile(file);
      }
    }

    int numDocs = this.indexer.numDocs();
    this.indexer.close();
    return numDocs;
  }

  /**
   * Searches the indexed files and prints the result to the stdout and also
   * returns it.
   * 
   * @param userQuery
   * @return
   * @throws IOException
   * @throws ParseException
   */
  public String search(String userQuery) throws IOException, ParseException {
    IndexSearcher searcher = new IndexSearcher(this.indexDir);
    QueryParser queryParser = new QueryParser(Version.LUCENE_30,
        FIELD_CONTENTS, this.analyzer);
    Query query = queryParser.parse(userQuery);
    TopDocs hits = searcher.search(query, 5);
    StringBuilder result = new StringBuilder();
    result.append(hits.totalHits + " matches for query [" + userQuery + "]");
    for (int i = 0; i < hits.scoreDocs.length; i++) {
      ScoreDoc scoreDoc = hits.scoreDocs[i];
      Document doc = searcher.doc(scoreDoc.doc);
      result.append("\n" + doc.get(FIELD_FILENAME) + "\n"
          + doc.get(FIELD_FULLPATH) + "\n" + doc.get(FIELD_CONTENTS));
    }
    System.out.println(result);
    return result.toString();
  }

  /**
   * Index the passed in file using {@link #indexer}.
   * 
   * @param file
   *          file to index. Should not be null.
   * @throws IOException
   */
  private void indexFile(File file) throws IOException {
    System.out.println("Indexing [" + file + "]");
    Document doc = new Document();
    doc.add(new Field(FIELD_CONTENTS, new FileReader(file)));
    doc.add(new Field(FIELD_FILENAME, file.getName(), Field.Store.YES,
        Field.Index.NOT_ANALYZED));
    doc.add(new Field(FIELD_FULLPATH, file.getCanonicalPath(), Field.Store.YES,
        Field.Index.NOT_ANALYZED));
    this.indexer.addDocument(doc);
  }

}
